﻿module internal IronLua.Parsing.Rewriter

open System
open System.Collections.Generic
open IronLua
open IronLua.Parsing
open IronLua.Parsing.Utilities

let rec optimizeone expr =
    optimizexpr expr |> List.head
and optimizexpr stmt = 
    match stmt with
    | Ast.Empty(p) ->                   [Cst.Empty(p)]
    | Ast.Number(p, d) ->               [Cst.Number(p, d)]
    | Ast.String(p, s) ->               [Cst.String(p, s)]
    | Ast.Boolean(p, b) ->              [Cst.Boolean(p, b)]
    | Ast.ClrObj(p, o) ->               [Cst.ClrObj(p, o)]
    | Ast.VarArgs(p) ->                 [Cst.VarArgs(p)]
    | Ast.VarArgsExpr(p) ->             [Cst.VarArgsExpr(p)]

    | Ast.Local(p, si, li, n) ->        [Cst.Local(p, si, li, n)]

    | Ast.Or(p, lvalue, rvalue) ->      [Cst.Binary(p, "__or", optimizeone lvalue, optimizeone rvalue)]
    | Ast.And(p, lvalue, rvalue) ->     [Cst.Binary(p, "__and", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Lt(p, lvalue, rvalue) ->      [Cst.Binary(p, "__lt", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Gt(p, lvalue, rvalue) ->      [Cst.Binary(p, "__gt", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Lte(p, lvalue, rvalue) ->     [Cst.Binary(p, "__le", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Gte(p, lvalue, rvalue) ->     [Cst.Binary(p, "__ge", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Eq(p, lvalue, rvalue) ->      [Cst.Binary(p, "__eq", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Neq(p, lvalue, rvalue) ->     [Cst.Unary(p, "__not", Cst.Binary(p, "__eq", optimizeone lvalue, optimizeone rvalue))]
    | Ast.Concat(p, lvalue, rvalue) ->  [Cst.Binary(p, "__concat", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Add(p, lvalue, rvalue) ->     [Cst.Binary(p, "__add", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Sub(p, lvalue, rvalue) ->     [Cst.Binary(p, "__sub", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Mul(p, lvalue, rvalue) ->     [Cst.Binary(p, "__mul", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Div(p, lvalue, rvalue) ->     [Cst.Binary(p, "__div", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Mod(p, lvalue, rvalue) ->     [Cst.Binary(p, "__mod", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Unm(p, lvalue) ->             [Cst.Unary(p, "__unm", optimizeone lvalue)]
    | Ast.Una(p, lvalue) ->             [Cst.Unary(p, "__una", optimizeone lvalue)]
    | Ast.Len(p, lvalue) ->             [Cst.Unary(p, "__len", optimizeone lvalue)]
    | Ast.Pow(p, lvalue, rvalue) ->     [Cst.Binary(p, "__pow", optimizeone lvalue, optimizeone rvalue)]
    | Ast.Label(p, lbl) ->              [Cst.Label(p, lbl)]
    | Ast.Goto(p, lbl) ->               [Cst.Goto(p, lbl)]
    | Ast.Call(p, c, parms) ->          [Cst.Call(p, optimizeone c, optimizexprs parms)]
    | Ast.Return(p, values) ->          [Cst.Return(p, optimizexprs values)]

    | Ast.Table(p, items) ->            optimizetablector p items
    | Ast.LocalDecl(p, lvalues, rvalues) -> optimizelocaldecl p lvalues rvalues                                        
    | _ as unk ->                       LuaRuntimeException(sprintf "unexpected %A" unk) |> raise

and optimizelocaldecl p lvalues rvalues =     
    let larr = lvalues |> List.toArray
    let rarr = rvalues |> optimizexprs |> List.toArray
    let getpair (valarray : Cst.Expr array) index lvalue =
        let astexpr = lvalue
        let cstexpr = if index < valarray.Length then valarray.[index] else Cst.Nil(TokenPosition.Virtual)
        (astexpr, cstexpr)
    let astcstpairs = larr |> Array.mapi (getpair rarr)
    let deflocs (ast, cst) = 
        let p, si, li, n = match ast with
                           | Ast.Local(p, si, li, n) -> (p, si, li, n)
                           | _ as unk ->                LuaRuntimeException(sprintf "local expected at %A" unk.Position) |> raise
        Cst.DefineLocal(p, si, li, n, cst)
    astcstpairs |> Array.map deflocs |> Array.toList

and optimizetablector p items =
    failwith "optimizetable"

and optimizexprs stmts =
    stmts |> List.map optimizexpr |> List.concat |> Seq.toList

let optimizeall stmts =
    let newstmts = optimizexprs stmts
    let upn, locn = (0, 0)
    (newstmts, upn, locn)

let optimize ast = 
    match ast with
    | Ast.FunDecl(pos, id, expr, name, parms, block, upn, locn, vars) as fn ->             
        match expr with
        | Ast.Empty(pos) ->             ignore()
        | _ as unk ->                   LuaRuntimeException(sprintf "non-member function expected, got %A" unk) |> raise
        match block with
        | Ast.Block(range, stmts) ->    let newstmts, upn, locn = optimizeall stmts
                                        Cst.Chunk(pos, id, name, optimizexprs parms, Cst.Scope(range, newstmts), upn, locn)
        | _ ->                          LuaRuntimeException("function body expected") |> raise
    | _ as unk ->                   LuaRuntimeException(sprintf "function expected, got %A" unk) |> raise